﻿//MarketDepthGenerator.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Testing
{
	using System;
	using System.Collections.Generic;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Генератор стакана случайным методом.
	/// </summary>
	public abstract class MarketDepthGenerator : MarketDataGenerator<MarketDepth>
	{
		/// <summary>
		/// Инициализировать <see cref="MarketDepthGenerator"/>.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо генерировать данные.</param>
		protected MarketDepthGenerator(Security security)
			: base(security)
		{
			UseTradeVolume = true;
			MinSpreadStepCount = 1;
			MaxSpreadStepCount = int.MaxValue;
			MaxBidsDepth = 10;
			MaxAsksDepth = 10;
			MaxGenerations = 20;
		}


		/// <summary>
		/// Использовать для генерации лучших котировок в стакане объем исторических сделок.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию true.
		/// </remarks>
		public bool UseTradeVolume { get; set; }

		private int _minSpreadStepCount;

		/// <summary>
		/// Минимальная величина спреда между лучшими котировками в единицах числа шагов цены.
		/// Величина спреда будет выбрана случайно между <see cref="MinSpreadStepCount"/> и <see cref="MaxSpreadStepCount"/>.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию 1.
		/// </remarks>
		public int MinSpreadStepCount
		{
			get { return _minSpreadStepCount; }
			set
			{
				if (value < 1)
					throw new ArgumentOutOfRangeException("value", value, "Минимальная величина спреда не может быть меньше одного.");

				_minSpreadStepCount = value;
			}
		}

		private int _maxSpreadStepCount;

		/// <summary>
		/// Максимальная величина спреда между лучшими котировками в единицах числа шагов цены.
		/// Величина спреда будет выбрана случайно между <see cref="MinSpreadStepCount"/> и <see cref="MaxSpreadStepCount"/>.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию <see cref="int.MaxValue"/>.
		/// </remarks>
		public int MaxSpreadStepCount
		{
			get { return _maxSpreadStepCount; }
			set
			{
				if (value < 1)
					throw new ArgumentOutOfRangeException("value", value, "Максимальная величина спреда не может быть меньше одного.");

				_maxSpreadStepCount = value;
			}
		}

		private int _maxBidsDepth;

		/// <summary>
		/// Максимальная глубина бидов.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию равно 1.
		/// </remarks>
		public int MaxBidsDepth
		{
			get { return _maxBidsDepth; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, "Максимальная глубина бидов не может быть меньше нуля.");

				_maxBidsDepth = value;
			}
		}

		private int _maxAsksDepth;

		/// <summary>
		/// Максимальная глубина офферов.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию равно 1.
		/// </remarks>
		public int MaxAsksDepth
		{
			get { return _maxAsksDepth; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, "Максимальная глубина офферов не может быть меньше нуля.");

				_maxAsksDepth = value;
			}
		}

		/// <summary>
		/// Генерировать ли стаканы после каждой сделки. По умолчанию false.
		/// </summary>
		public bool GenerateDepthOnEachTrade { get; set; }

		private int _maxGenerations;

		/// <summary>
		/// Максимальное количество генераций после последнего поступления исходных данных для стакана.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию равно 20.
		/// </remarks>
		public int MaxGenerations
		{
			get { return _maxGenerations; }
			set
			{
				if (value < 1)
					throw new ArgumentOutOfRangeException("value", value, "Максимальное количество генераций не может быть меньше одного.");

				_maxGenerations = value;
			}
		}

		/// <summary>
		/// Сгенерировать стакан.
		/// </summary>
		/// <param name="time">Биржевое время.</param>
		/// <returns>Сгенерированный стакан.</returns>
		public MarketDepth Generate(DateTime time)
		{
			var depth = new MarketDepth(Security);
			Generate(depth, time);
			return depth;
		}

		/// <summary>
		/// Обновить генератор данными о сделках.
		/// </summary>
		/// <param name="trades">Сделки.</param>
		public abstract void UpdateTrades(IEnumerable<Trade> trades);

		/// <summary>
		/// Создать случайным методом котировку.
		/// </summary>
		/// <param name="startPrice">Начальная цена, от которой случайным методом необходимо получить цену котировки.</param>
		/// <param name="direction">Направление котировки.</param>
		/// <returns>Случайная котировка.</returns>
		protected Quote CreateQuote(decimal startPrice, OrderDirections direction)
		{
			var price = startPrice + (direction == OrderDirections.Sell ? 1 : -1) * Steps.Next() * Security.MinStepSize;

			if (price <= 0)
				price = Security.MinStepSize;

			return new Quote
			{
				OrderDirection = direction,
				Price = price,
				Security = Security,
				Volume = Volumes.Next(),
			};
		}

		/// <summary>
		/// Требуется ли генерация новых данных.
		/// </summary>
		/// <param name="time">Текущее время.</param>
		/// <returns>true если надо сгенерировать данные.</returns>
		public override bool IsTimeToGenerate(DateTime time)
		{
			return base.IsTimeToGenerate(time) && Security.IsTradeTime(time);
		}
	}

	/// <summary>
	/// Генератор стаканов, учитывающий последовательность сделок.
	/// </summary>
	public class TrendMarketDepthGenerator : MarketDepthGenerator
	{
		private bool _newTrades;

		// не генерировать стаканы, если у нас давно не было сделок
		private int _currGenerations;

		private Trade _lastTrade;
		private Trade _prevTrade;
		private Trade _bestAsk;
		private Trade _bestBid;

		/// <summary>
		/// Создать <see cref="TrendMarketDepthGenerator"/>.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо генерировать данные.</param>
		public TrendMarketDepthGenerator(Security security)
			: base(security)
		{
		}

		/// <summary>
		/// Инициализировать состояние генератора.
		/// </summary>
		public override void Init()
		{
			base.Init();

			_lastTrade = null;
			_prevTrade = null;
			_bestAsk = null;
			_bestBid = null;

			_newTrades = false;
			_currGenerations = 0;
		}

		/// <summary>
		/// Обновить генератор данными о сделках.
		/// </summary>
		/// <param name="trades">Сделки.</param>
		public override void UpdateTrades(IEnumerable<Trade> trades)
		{
			foreach (var trade in trades)
			{
				if (null == _prevTrade)
				{
					_prevTrade = trade;
					_bestAsk = trade;
					_bestBid = trade;
				}

				if (trade.OrderDirection == null)
				{
					if (trade.Price > _prevTrade.Price)
					{
						_bestAsk = trade;
						//BestBid = PrevTrade;
						_prevTrade = trade;
					}
					else if (trade.Price < _prevTrade.Price)
					{
						_bestBid = trade;
						//BestAsk = PrevTrade;
						_prevTrade = trade;
					}
				}
				else
				{
					if (trade.OrderDirection == OrderDirections.Buy)
						_bestAsk = trade;
					else
						_bestBid = trade;
				}

				_lastTrade = trade;
				_newTrades = true;
			}

			_currGenerations = MaxGenerations;
		}

		/// <summary>
		/// Сгенерировать стакан, имитируя спред по направлению изменения цены последних 2х сделок.
		/// </summary>
		/// <param name="data">Маркет-данные.</param>
		/// <param name="time">Текущее время.</param>
		public override void Generate(MarketDepth data, DateTime time)
		{
			if (_bestBid == null || _bestAsk == null)
			{
				if (_lastTrade == null)
					throw new InvalidOperationException("Информация о последней сделке отсутствует.");

				_bestBid = _bestAsk = _lastTrade;
			}

			if (_currGenerations == 0)
				throw new InvalidOperationException("Невозможно сгенерировать новый стакан, так как длительное время не было новых сделок.");

			var bidPrice = _bestBid.Price;
			var askPrice = _bestAsk.Price;

			var minSpred = MinSpreadStepCount * Security.MinStepSize;
			var maxStread = MaxSpreadStepCount * Security.MinStepSize;

			if ((askPrice - bidPrice) < minSpred)
			{
				if (_bestBid == _lastTrade) // тренд вниз
					askPrice = bidPrice + minSpred;
				else
					bidPrice = askPrice - minSpred;
			}
			else if ((askPrice - bidPrice) > maxStread)
			{
				if (_bestBid == _lastTrade) // тренд вниз
					askPrice = bidPrice + maxStread;
				else
					bidPrice = askPrice - maxStread;
			}

			var bids = new List<Quote>
			{
				new Quote
				{
					OrderDirection = OrderDirections.Buy,
					Price = bidPrice,
					Security = Security,
					Volume = UseTradeVolume ? _bestBid.Volume : Volumes.Next(),
				}
			};

			var count = MaxBidsDepth - bids.Count;

			for (var i = 0; i < count; i++)
			{
				var quote = CreateQuote(bidPrice, OrderDirections.Buy);

				if (quote.Price <= 0)
					break;

				bids.Add(quote);
				bidPrice = quote.Price;
			}

			var asks = new List<Quote>
			{
				new Quote
				{
					OrderDirection = OrderDirections.Sell,
					Price = askPrice,
					Security = Security,
					Volume = UseTradeVolume ? _bestAsk.Volume : Volumes.Next(),
				}
			};

			count = MaxAsksDepth - asks.Count;

			for (var i = 0; i < count; i++)
			{
				var quote = CreateQuote(askPrice, OrderDirections.Sell);

				if (quote.Price <= 0)
					break;

				asks.Add(quote);
				askPrice = quote.Price;
			}

			data.Update(bids, asks, true, time);

			_newTrades = false;

			_currGenerations--;

			base.Generate(data, time);
		}

		/// <summary>
		/// Нужно ли генерировать ли новый стакан.
		/// </summary>
		/// <param name="time">Биржевое время.</param>
		/// <returns>true если надо сгенерировать данные.</returns>
		public override bool IsTimeToGenerate(DateTime time)
		{
			if (_currGenerations == 0)
				return false;

			if (_bestBid == null || _bestAsk == null)
				return false;

			return GenerateDepthOnEachTrade && _newTrades
				? Security.IsTradeTime(time)
				: base.IsTimeToGenerate(time);
		}

		/// <summary>
		/// Создать копию генератора.
		/// </summary>
		/// <returns>Копия.</returns>
		public override MarketDataGenerator<MarketDepth> Clone()
		{
			return new TrendMarketDepthGenerator(Security)
			{
				MaxVolume = MaxVolume,
				MinVolume = MinVolume,
				MaxPriceStepCount = MaxPriceStepCount,
				Interval = Interval,
				Volumes = Volumes,
				Steps = Steps,

				UseTradeVolume = UseTradeVolume,
				MinSpreadStepCount = MinSpreadStepCount,
				MaxSpreadStepCount = MaxSpreadStepCount,
				MaxBidsDepth = MaxBidsDepth,
				MaxAsksDepth = MaxAsksDepth,
				MaxGenerations = MaxGenerations,
			};
		}
	}
}